// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

// Custom window messages
#define CM_UPDATE_TITLE          (WM_USER)

template <typename T>
class BaseWindow
{
public:
    virtual ~BaseWindow() = 0;
    static T* GetThisFromHandle(HWND const window) noexcept
    {
        return reinterpret_cast<T *>(GetWindowLongPtr(window, GWLP_USERDATA));
    }

    static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
    {
        WINRT_ASSERT(window);

        if (WM_NCCREATE == message)
        {
            auto cs = reinterpret_cast<CREATESTRUCT *>(lparam);
            T* that = static_cast<T*>(cs->lpCreateParams);
            WINRT_ASSERT(that);
            WINRT_ASSERT(!that->_window);
            that->_window = window;
            SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));

            EnableNonClientDpiScaling(window);
            that->_currentDpi = GetDpiForWindow(window);
        }
        else if (T* that = GetThisFromHandle(window))
        {
            return that->MessageHandler(message, wparam, lparam);
        }

        return DefWindowProc(window, message, wparam, lparam);
    }

    virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
    {
        switch (message) {
        case WM_DPICHANGED:
        {
            return HandleDpiChange(_window, wparam, lparam);
        }

        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }

        case WM_SIZE:
        {
            UINT width = LOWORD(lparam);
            UINT height = HIWORD(lparam);

            switch (wparam)
            {
            case SIZE_MAXIMIZED:
                [[fallthrough]];
            case SIZE_RESTORED:
                if (_minimized)
                {
                    _minimized = false;
                    OnRestore();
                }

                // We always need to fire the resize event, even when we're transitioning from minimized.
                // We might be transitioning directly from minimized to maximized, and we'll need
                // to trigger any size-related content changes.
                OnResize(width, height);
                break;
            case SIZE_MINIMIZED:
                if (!_minimized)
                {
                    _minimized = true;
                    OnMinimize();
                }
                break;
            default:
                // do nothing.
                break;
            }
        }
        case CM_UPDATE_TITLE:
        {

            SetWindowTextW(_window, _title.c_str());
            break;
        }
        }

        return DefWindowProc(_window, message, wparam, lparam);
    }

    // DPI Change handler. on WM_DPICHANGE resize the window
    LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam)
    {
        _inDpiChange = true;
        const HWND hWndStatic = GetWindow(hWnd, GW_CHILD);
        if (hWndStatic != nullptr)
        {
            const UINT uDpi = HIWORD(wParam);

            // Resize the window
            auto lprcNewScale = reinterpret_cast<RECT*>(lParam);

            SetWindowPos(hWnd, nullptr, lprcNewScale->left, lprcNewScale->top,
                lprcNewScale->right - lprcNewScale->left, lprcNewScale->bottom - lprcNewScale->top,
                SWP_NOZORDER | SWP_NOACTIVATE);

            _currentDpi = uDpi;
            NewScale(uDpi);
        }
        _inDpiChange = false;
        return 0;
    }

    virtual void NewScale(UINT dpi) = 0;

    virtual void OnResize(const UINT width, const UINT height) = 0;
    virtual void OnMinimize() = 0;
    virtual void OnRestore() = 0;

    RECT GetWindowRect() const
    {
        RECT rc = { 0 };
        ::GetWindowRect(_window, &rc);
        return rc;
    }

    HWND GetHandle() noexcept
    {
        return _window;
    };

    // Method Description:
    // - Sends a message to our message loop to update the title of the window.
    // Arguments:
    // - newTitle: a string to use as the new title of the window.
    // Return Value:
    // - <none>
    void UpdateTitle(std::wstring_view newTitle)
    {
        _title = newTitle;
        PostMessageW(_window, CM_UPDATE_TITLE, 0, reinterpret_cast<LPARAM>(nullptr));
    };

protected:
    using base_type = BaseWindow<T>;
    HWND _window = nullptr;

    unsigned int _currentDpi = 0;
    bool _inDpiChange = false;

    std::wstring _title = L"";

    bool _minimized = false;
};

template <typename T>
inline BaseWindow<T>::~BaseWindow() { }
